// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license


#include "stdafx.h"
#include "WorldBuilder.h"
#include "OutputWnd.h"
#include "Resource.h"
#include "MainFrm.h"
#include "SciLexer.h"
#include "Shader.h"
#include <fstream>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

const TCHAR pyKeyWords[] = 
    _T("and del for is raise \
        assert elif from lambda return    \
        break else global not try \
        class except if or while \
        continue exec import pass yield \
        def finally in print");

// Not only keywords but also intrinsic functions and variables
const TCHAR glslKeyWords[] = 
    _T("attribute const uniform \
        varying layout centroid flat smooth noperspective \
        break continue do for while switch \
        case default if else in out inout float int \
        void bool true false invariant discard return main \
        lowp mediump highp precision\
        mat2 mat3 mat4 mat2x2 mat2x3 mat2x4 \
        mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4 \
        vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 \
        bvec3 bvec4 uint uvec2 uvec3 uvec4 \
        lowp mediump highp precision \
        sampler1D sampler2D sampler3D \
        samplerCube sampler1DShadow sampler2DShadow \
        samplerCubeShadow sampler1DArray sampler2DArray \
        sampler1DArrayShadow sampler2DArrayShadow \
        isampler1D isampler2D isampler3D \
        isamplerCube isampler1DArray isampler2DArray \
        usampler1D usampler2D usampler3D \
        usamplerCube usampler1DArray usampler2DArray \
        sampler2DRect sampler2DRectShadow isampler2DRect \
        usampler2DRect samplerBuffer isamplerBuffer \
        usamplerBuffer sampler2DMS isampler2DMS usampler2DMS \
        sampler2DMSArray isampler2DMSArray \
        usampler2DMSArray struct \
        gl_Position gl_PointSize gl_ClipVertex \
        gl_FragCoord gl_FrontFacing \
        gl_FragColor gl_FragData gl_FragDepth \
        gl_Color gl_SecondaryColor gl_Normal \
        gl_Vertex gl_FogCoord \
        gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 \
        gl_MultiTexCoord4 gl_MultiTexCoord5 \
        gl_MultiTexCoord6 gl_MultiTexCoord7 gl_MultiTexCoord8 gl_MultiTexCoord9 \
        gl_MultiTexCoord10 gl_MultiTexCoord11 \
        gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor \
        gl_TexCoord gl_FogFragCoord \
        gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix \
        gl_NormalMatrix gl_TextureMatrix \
        gl_NormalScale gl_DepthRange gl_ClipPlane gl_Point gl_FrontMaterial \
        gl_BackMaterial gl_LightSource gl_LightModel \
        gl_FrontLightModelProduct gl_BackLightModelProduct gl_FrontLightProduct \
        gl_BackLightProduct gl_TextureEnvColor gl_Fog \
        gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ gl_ObjectPlaneS \
        gl_ObjectPlaneT gl_ObjectPlaneR gl_ObjectPlaneQ \
        gl_ModelViewMatrixInverse gl_ProjectionMatrixInverse \
        gl_ModelViewProjectionMatrixInverse gl_TextureMatrixInverse \
        gl_ModelViewMatrixTranspose gl_ProjectionMatrixTranspose \
        gl_ModelViewProjectionMatrixTranspose gl_TextureMatrixTranspose \
        gl_ModelViewMatrixInverseTranspose gl_ProjectionMatrixInverseTranspose \
        gl_ModelViewProjectionMatrixInverseTranspose \
        gl_TextureMatrixInverseTranspose gl_DepthRangeParameters \
        gl_PointParameters gl_MaterialParameters \
        gl_LightSourceParameters gl_LightModelParameters gl_LightModelProducts \
        gl_LightProducts gl_FogParameters gl_MaxLights \
        gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords \
        gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats \
        gl_MaxVertexTextureImageUnits gl_MaxCombinedTextureImageUnits \
        gl_MaxTextureImageUnits gl_MaxFragmentUniformComponents \
        gl_MaxDrawBuffers radians degrees sin cos tan asin acos atan \
        pow exp2 log2 sqrt inversesqrt abs sign floor ceil fract mod \
        min max clamp mix step smoothstep \
        length distance dot cross normalize \
        ftransform faceforward reflect matrixcompmult \
        lessThan lessThanEqual greaterThan greaterThanEqual \
        equal notEqual any all not textureSize texture textureProj textureLod \
        textureOffset texelFetch texelFetchOffset \
        textureProjOffset textureLodOffset textureProjLod textureProjLodOffset \
        textureGrad textureGradOffset textureProjGrad textureProjGradOffset \
        texture1D texture1DProj texture1DLod texture1DProjLod \
        texture2D texture2DProj texture2DLod texture2DProjLod \
        texture3D texture3DProj texture3DLod texture3DProjLod \
        textureCube textureCubeLod shadow1D shadow1DProj \
        shadow1DLod shadow1DProjLod shadow2D shadow2DProj \
        shadow2DLod shadow2DProjLod dFdx dFdy fwidth noise1 noise2 noise3 \
        noise4 refract exp log");


/////////////////////////////////////////////////////////////////////////////
// COutputBar

COutputWnd::COutputWnd()
: UiFollower(UiFollower::IsLogListener | UiFollower::IsSelectionListener)
{
}

COutputWnd::~COutputWnd()
{
}

BEGIN_MESSAGE_MAP(COutputWnd, CDockablePane)
    ON_WM_CREATE()
    ON_WM_SIZE()
    ON_REGISTERED_MESSAGE(AFX_WM_CHANGE_ACTIVE_TAB, OnTabChange)
    ON_COMMAND(ID_PY_UNDO, OnPythonUndo)
    ON_COMMAND(ID_PY_REDO, OnPythonRedo)
    ON_COMMAND(ID_PY_COPY, OnPythonCopy)
    ON_COMMAND(ID_PY_CUT, OnPythonCut)
    ON_COMMAND(ID_PY_PASTE, OnPythonPaste)
    ON_COMMAND(ID_PY_OPEN, OnPythonLoad)
    ON_COMMAND(ID_PY_SAVE, OnPythonSave)
    ON_COMMAND(ID_PY_RUN, OnPythonRun)
    ON_COMMAND(ID_PY_RUN, OnPythonStop)
    ON_COMMAND(ID_SHADER_SETCODE, OnShaderSetCode)
    ON_WM_CHAR()
END_MESSAGE_MAP()

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void COutputWnd::setAStyle(CScintillaCtrl& rCtrl, 
                           int style, COLORREF fore, 
                           COLORREF back, int size, const char* face) 
{
    rCtrl.StyleSetFore(style, fore);
    rCtrl.StyleSetBack(style, back);
    if (size >= 1)
    {
        rCtrl.StyleSetSize(style, size);
    }
    if (face) 
    {
        rCtrl.StyleSetFont(style, face);
    }
}

void COutputWnd::setSyntaxColors(CScintillaCtrl& rCtrl)
{
    setAStyle(rCtrl, STYLE_DEFAULT, RGB(0, 0, 0), RGB(0xff, 0xff, 0xff), 9, "Consolas");
    rCtrl.StyleClearAll();
    setAStyle(rCtrl, SCE_C_DEFAULT, RGB(0, 0, 0));
    setAStyle(rCtrl, SCE_C_COMMENT, RGB(0, 0x80, 0));
    setAStyle(rCtrl, SCE_C_COMMENTLINE, RGB(0, 0x80, 0));
    setAStyle(rCtrl, SCE_C_COMMENTDOC, RGB(0, 0x80, 0));
    setAStyle(rCtrl, SCE_C_COMMENTLINEDOC, RGB(0, 0x80, 0));
    setAStyle(rCtrl, SCE_C_COMMENTDOCKEYWORD, RGB(0, 0x80, 0));
    setAStyle(rCtrl, SCE_C_COMMENTDOCKEYWORDERROR, RGB(0, 0x80, 0));
    setAStyle(rCtrl, SCE_C_NUMBER, RGB(0, 0x80, 0x80));
    setAStyle(rCtrl, SCE_C_WORD, RGB(0, 0, 0x80));
    setAStyle(rCtrl, SCE_C_WORD2, RGB(0, 0x80, 0));
    rCtrl.StyleSetBold(SCE_C_WORD, 1);
    setAStyle(rCtrl, SCE_C_STRING, RGB(0x80, 0, 0x80));
    setAStyle(rCtrl, SCE_C_IDENTIFIER, RGB(0x80, 0, 0));
    setAStyle(rCtrl, SCE_C_PREPROCESSOR, RGB(0x80, 0, 0));
    setAStyle(rCtrl, SCE_C_OPERATOR, RGB(0x80, 0x80, 0));
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void COutputWnd::createEditorToolbar(CEditorToolBar& toolBar, 
                                     int resourceId)
{
    toolBar.Create(this, AFX_DEFAULT_TOOLBAR_STYLE, resourceId);
    toolBar.LoadToolBar(resourceId, 0, 0, TRUE /* Is locked */);

    toolBar.SetPaneStyle(
        toolBar.GetPaneStyle() | CBRS_TOOLTIPS | CBRS_FLYBY);
    toolBar.SetPaneStyle(
        toolBar.GetPaneStyle() &
            ~(CBRS_GRIPPER | CBRS_SIZE_DYNAMIC | CBRS_BORDER_TOP 
              | CBRS_BORDER_BOTTOM | CBRS_BORDER_LEFT | CBRS_BORDER_RIGHT));
    toolBar.SetOwner(this);

    // All commands will be routed via this control , not via the parent frame:
    toolBar.SetRouteCommandsViaFrame(FALSE);
}

int COutputWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CDockablePane::OnCreate(lpCreateStruct) == -1)
        return -1;

    m_Font.CreateStockObject(DEFAULT_GUI_FONT);

    CRect rectDummy;
    rectDummy.SetRectEmpty();

    // Create tabs window:
    if (!m_wndTabs.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, 1))
    {
        TRACE0("Failed to create output tab window\n");
        return -1;      // fail to create
    }

    // Create editor toolbars
    createEditorToolbar(m_wndPythonToolBar, IDR_PYTHON);
    createEditorToolbar(m_wndShaderToolBar, IDR_SHADER);

    // We'll use this to assign child Id's
    int cnt = 1;

    // Create output panes:
    const DWORD dwStyle = LBS_NOINTEGRALHEIGHT | WS_CHILD 
                          | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL;

    if (!m_wndOutputLog.Create(dwStyle, rectDummy, &m_wndTabs, cnt++))
    {
        TRACE0("Failed to create output windows\n");
        return -1;      // fail to create
    }

    // Create python editor
    if(!m_wndPython.Create(WS_CHILD|WS_VISIBLE|WS_TABSTOP, 
                           rectDummy, &m_wndTabs, cnt++))
    {
        TRACE0("Failed to create output windows\n");
        return -1;      // fail to create
    }

    // Create shader editor
    if(!m_wndShader.Create(WS_CHILD|WS_VISIBLE|WS_TABSTOP, 
                           rectDummy, &m_wndTabs, cnt++))
    {
        TRACE0("Failed to create output windows\n");
        return -1;      // fail to create
    }

    // By default the Scintilla control uses LF + CR as newline 
    // character and Python doesn't like that at all. So we specify
    // that newline should only contain LF
    m_wndPython.SetEOLMode(SC_EOL_LF);

    // Setup for Python editing
    m_wndPython.SetLexer(SCLEX_PYTHON);
    m_wndPython.SetKeyWords(0, pyKeyWords);
    setSyntaxColors(m_wndPython);

    // Setup for Shader editor
    m_wndShader.SetLexer(SCLEX_CPP);
    m_wndShader.SetKeyWords(0, glslKeyWords);
    setSyntaxColors(m_wndShader);



    m_wndOutputLog.SetFont(&m_Font);

    CString strTabName;
    BOOL bNameValid;

    cnt = 0;

    // Attach list windows to tab:
    bNameValid = strTabName.LoadString(IDS_BUILD_TAB);
    ASSERT(bNameValid);
    m_wndTabs.AddTab(&m_wndOutputLog, strTabName, cnt++);

    bNameValid = strTabName.LoadString(IDS_PYTHON_TAB);
    ASSERT(bNameValid);
    m_wndTabs.AddTab(&m_wndPython, strTabName, cnt++);

    bNameValid = strTabName.LoadString(IDS_SHADER_TAB);
    ASSERT(bNameValid);
    m_wndTabs.AddTab(&m_wndShader, strTabName, cnt++);
    m_shaderTabIndex = m_wndTabs.GetTabsNum() - 1;

    return 0;
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

CSize COutputWnd::adjustEditorToolBarLayout(const CRect& rectClient, 
                                            CEditorToolBar& toolBar)
{
    bool useVerticalToolBars = true;

    CSize offset(0, 0);

    CSize pyToolbarSize = 
        toolBar.CalcFixedLayout(FALSE, FALSE);
    
    offset.cx = useVerticalToolBars ? pyToolbarSize.cx : 0;
    offset.cy = useVerticalToolBars ? 0 : pyToolbarSize.cy;

    toolBar.SetWindowPos(NULL, 
        rectClient.left,                
        rectClient.top,                 
        useVerticalToolBars ? pyToolbarSize.cx : rectClient.Width(),
        useVerticalToolBars ? rectClient.Height() : pyToolbarSize.cy,
        SWP_NOACTIVATE | SWP_NOZORDER);

    return offset;
}

void COutputWnd::AdjustLayout()
{
    if (GetSafeHwnd() == NULL)
    {
        return;
    }

    // Make the wnd have the same title as the selected tab
    CString tabName;
    m_wndTabs.GetTabLabel(m_wndTabs.GetActiveTab(), tabName);
    this->SetWindowText(tabName);


    CRect rectClient, rectCombo;
    GetClientRect(rectClient);

    bool isPythonActive = (m_wndTabs.GetActiveWnd() == &m_wndPython);
    m_wndPythonToolBar.ShowWindow(isPythonActive ? SW_SHOW : SW_HIDE);

    bool isShaderActive = (m_wndTabs.GetActiveWnd() == &m_wndShader);
    m_wndShaderToolBar.ShowWindow(isShaderActive ? SW_SHOW : SW_HIDE);


    CSize offset;
    if(isPythonActive)
    {
        offset = adjustEditorToolBarLayout(rectClient, m_wndPythonToolBar);
    }
    else if(isShaderActive)
    {
        offset = adjustEditorToolBarLayout(rectClient, m_wndShaderToolBar);
    }

    m_wndTabs.SetWindowPos(
        NULL, 
        offset.cx, 
        offset.cy, 
        rectClient.Width() - offset.cx, 
        rectClient.Height() - offset.cy, 
        SWP_NOACTIVATE | SWP_NOZORDER);
}

void COutputWnd::OnSize(UINT nType, int cx, int cy)
{
    CDockablePane::OnSize(nType, cx, cy);
    AdjustLayout();
    
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void COutputWnd::onMessage(LogManager* /*sender*/, const std::string & msg)
{
    // TODO there has to be a better way to convert from std::string to Unicode CString 
    // TODO use std::wstring instead of std::string all over the app
    m_wndOutputLog.InsertString(0, CString(CStringA(msg.c_str())));
    m_wndOutputLog.SetCurSel(0);
}

void COutputWnd::onSelectionChanged(SelectionManager* sender)
{
    Selectable* sel = sender->getSelection();
    Shader* shader = dynamic_cast<Shader*>(sel);
    if(shader != NULL)
    {
        m_wndTabs.SetActiveTab(m_shaderTabIndex);
        m_wndShader.ClearAll();

        m_wndShader.SetReadOnly(false);

        CA2T unicode(shader->getCode().c_str());
        m_wndShader.SetText(unicode);
    }
    else
    {
        m_wndShader.SetText(L"No shader selected");
        m_wndShader.SetReadOnly(true);
    }
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

std::string COutputWnd::getEditorText(CScintillaCtrl& editor)
{
    // Don't know why the "+1" part is necessary
    int len = editor.GetLength() + 1;
    CString unicodeTxt = editor.GetText(len);

    // unicode => ansi encoding conversion
    CT2CA ansiTxt(unicodeTxt);
    return (LPCSTR)ansiTxt;
}

// -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

void COutputWnd::OnPythonCopy()
{
    m_wndPython.Copy();
}

void COutputWnd::OnPythonPaste()
{
    m_wndPython.Paste();
}

void COutputWnd::OnPythonCut()
{
    m_wndPython.Cut();
}

// -  -  -  -  -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -

void COutputWnd::OnPythonUndo()
{
    m_wndPython.Undo();
}

void COutputWnd::OnPythonRedo()
{
    m_wndPython.Redo();
}

// -  -  -  -  -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -

namespace
{

// The file dialog filter for python scripts
const TCHAR filter[] = _T("Python scripts (*.py)|*.py||");

}

void COutputWnd::OnPythonLoad()
{
    // Launch file dialog to get filename from user
    CFileDialog dlg(true, _T("*.py"), NULL, 0, ::filter);
    if(dlg.DoModal() != IDOK)
    {
        return;
    }

    // Convert filename from unicode to ansi
    CT2A ansiFileName(dlg.GetPathName());

    // Read script line by line. Append "\n" to each line
    // because getline eats those
    // TODO this is repeated in ShaderProgram.cpp
    std::string script;
    std::string line;
    std::ifstream ifs((LPCSTR)ansiFileName);
    while(std::getline(ifs, line))
    {
        line.append("\n");
        script.append(line);
    }

    // Replace current script with the text we have just read
    m_wndPython.ClearAll();
    m_wndPython.SetText(script.c_str());
}

// -  -  -  -  -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -

void COutputWnd::OnPythonSave()
{
    // Launch file dialog to get filename from user
    CFileDialog dlg(false, _T("*.py"), NULL, 0, ::filter);
    if(dlg.DoModal() != IDOK)
    {
        return;
    }

    // Convert filename from unicode to ansi
    CT2A ansiFileName(dlg.GetPathName());

    // Grab script
    std::string str = getPythonScript();

    // Save it
    std::ofstream ofs((LPCSTR)ansiFileName);
    ofs << str;
}

// -  -  -  -  -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -

std::string COutputWnd::getPythonScript()
{
    return getEditorText(m_wndPython);
}

void COutputWnd::OnPythonRun()
{
    if(!pyManager().isRunning())
    {
        std::string script = getPythonScript();
        pyManager().runConcurrently(script);
    }
    else
    {
        AfxMessageBox(_T("Python interpreter already running"));
    }
}

void COutputWnd::OnPythonStop()
{
    if(pyManager().isRunning())
    {
        pyManager().stopCurrentScript();
    }
    else
    {
        AfxMessageBox(_T("There is no script running"));
    }
}

// -  -  -  -  -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -

std::string COutputWnd::getShaderCode()
{
    return getEditorText(m_wndShader);
}

void COutputWnd::OnShaderSetCode()
{
    std::string code = getShaderCode();
    Selectable* sel = getUi().getSelectionManager().getSelection();
    Shader* shader = dynamic_cast<Shader*>(sel);
    if(shader != NULL)
    {
        std::istringstream iss(code);
        shader->setCode(iss);
    }
}

// -  -  -  -  -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -

// Parameters:
// [in] wparam: The index of a tab
// [in] lparam: Not used
// Return value: Nonzero

LRESULT COutputWnd::OnTabChange(WPARAM, LPARAM)
{
    AdjustLayout();
    return 1;
}

// -  -  -  -  -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -  - -  -  -

bool COutputWnd::hasFocus() const
{
    // This should check the other tabs but only the py tab responds
    // to accelerator tabs so checking only for it is probably enough
    return ::GetFocus() == m_wndPython;
}


/////////////////////////////////////////////////////////////////////////////
// COutputList1

COutputList::COutputList()
{
}

COutputList::~COutputList()
{
}

BEGIN_MESSAGE_MAP(COutputList, CListBox)
    ON_WM_CONTEXTMENU()
    ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
    ON_COMMAND(ID_EDIT_CLEAR, OnEditClear)
    ON_COMMAND(ID_VIEW_OUTPUTWND, OnViewOutput)
    ON_WM_WINDOWPOSCHANGING()
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// COutputList message handlers

void COutputList::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
{
    CMenu menu;
    menu.LoadMenu(IDR_OUTPUT_POPUP);

    CMenu* pSumMenu = menu.GetSubMenu(0);

    if (AfxGetMainWnd()->IsKindOf(RUNTIME_CLASS(CMDIFrameWndEx)))
    {
        CMFCPopupMenu* pPopupMenu = new CMFCPopupMenu;

        if (!pPopupMenu->Create(this, point.x, point.y, 
                                (HMENU)pSumMenu->m_hMenu, FALSE, TRUE))
            return;

        ((CMDIFrameWndEx*)AfxGetMainWnd())->OnShowPopupMenu(pPopupMenu);
        UpdateDialogControls(this, FALSE);
    }

    SetFocus();
}

void COutputList::OnEditCopy()
{
    MessageBox(_T("Copy output"));
}

void COutputList::OnEditClear()
{
    MessageBox(_T("Clear output"));
}

void COutputList::OnViewOutput()
{
    CDockablePane* pParentBar = 
        DYNAMIC_DOWNCAST(CDockablePane, GetOwner());
    CMDIFrameWndEx* pMainFrame = 
        DYNAMIC_DOWNCAST(CMDIFrameWndEx, GetTopLevelFrame());

    if (pMainFrame != NULL && pParentBar != NULL)
    {
        pMainFrame->SetFocus();
        pMainFrame->ShowPane(pParentBar, FALSE, FALSE, FALSE);
        pMainFrame->RecalcLayout();

    }
}

